//#define DEBUG
#if (USE_LVGL_UI == 1)
#include "lvgl.h"
#endif

#include "mdb.h"

struct mdb_t mdb_context;

#define mdb_get()	(&mdb_context)
#define mnode_get(type)	(&(mdb_context.nodes[type]))

void __mdb_upt_point(int32_t dtype, int32_t newv);
void __mdb_set_external(int32_t dtype, int32_t newv, uint32_t dispmask);
void __mdb_set_pctool(int32_t dtype, int32_t newv, uint32_t dispmask);


int32_t mdb_index_from_addr(uint32_t addr)
{
	int i;
	struct mdb_t *mdb_p = mdb_get();
	struct mnode *node;

	for (i = 0; i < mdb_p->node_num; i++) {
		node = mnode_get(i);
		if(node->addr == addr) {
			return i;
		}
	}
			
	return -1;
}

static struct update_mnode *list_elem_find(struct mlist_head *head, struct simple_mnode *elem)
 {
 	struct update_mnode *uptnode;
	
	mlist_for_each_entry(uptnode, head, struct update_mnode, list) {
		if (uptnode->elem.addr == elem->addr && 
			uptnode->elem.flag == elem->flag) {
			return uptnode;
		}
	}

	return NULL;
 }

int mdb_update_insert(struct simple_mnode *elem)
{
//	int cnt = 0;
	struct mdb_t *mdb_p = mdb_get();
	struct update_mnode *upd_elem; 


	mdb_lock(mdb_p->lock);
	
	upd_elem = list_elem_find(&mdb_p->update_list, elem);
	if (elem->type != DB_WRITE_TYPE_UPT_POINT && upd_elem != NULL) { // addr exist
		upd_elem->elem.data = elem->data;
		mdb_unlock(mdb_p->lock);
		return 0;
	}

	// current not exit, insert it to list
	upd_elem = mdb_calloc(1, sizeof(struct update_mnode));
	if (!upd_elem) {
		log_error("mdb_calloc [upd_elem] memory fail\n");
		return -1;
	}

//	log_debug("db_insert_update: addr=[0x%04x] data=[0x%04x]\n",elem->addr, elem->data);
	mlist_add_tail(&upd_elem->list, &mdb_p->update_list);

	upd_elem->elem = *elem;

//	cnt = list_elem_count(&db_update_list);

//	log_debug("db_insert cnt=[%d]\n", list_elem_count(&db_update_list));

	mdb_unlock(mdb_p->lock);
	

	return 0;
}

static void mdb_update_handle(struct mdb_t *mdb_p)
{
	int32_t count;
	int32_t dtype = MDB_INVALID_TYPE;
	struct update_mnode *upd_elem;
	struct simple_mnode *elem;

//	log_info("mdb_update_handle enter\n");

retry:
	mdb_lock(mdb_p->lock);
	if (mlist_empty(&mdb_p->update_list)) {
//		log_info("update list empty, exit\n");
		mdb_unlock(mdb_p->lock);
		return;
	}

	count = mlist_elem_count(&mdb_p->update_list);
//	log_warn("mdb update elem count=[0%d]\n", count);
	
	upd_elem = mlist_first_entry(&mdb_p->update_list, struct update_mnode, list);
	elem = &upd_elem->elem;


	mlist_del(&upd_elem->list);
	mdb_unlock(mdb_p->lock);
	
	
	dtype = mdb_index_from_addr(elem->addr);
	if (dtype == MDB_INVALID_TYPE) {
		log_error("db update elem error: invalid addr(0x%x)\n", elem->addr);
		goto error;
	}	

	switch (elem->type) {
	case DB_WRITE_TYPE_UPT_POINT:
		__mdb_upt_point(dtype, elem->data);
		break;
	case DB_WRITE_TYPE_EXTERNAL:
//		__db_set_direct(dtype, elem->data, elem->flag);
		__mdb_set_external(dtype, elem->data, elem->flag);
		break;
	case DB_WRITE_TYPE_PCTOOL:
		__mdb_set_pctool(dtype, elem->data, elem->flag);
		break;
	}

error:
	mdb_free(upd_elem);
	
	count = mlist_elem_count(&mdb_p->update_list);
//	log_warn("mdb_update_handle retry count=[%d]\n", count);

	goto retry;
}

/*  do not change data, just call dispatch callback  */
void mdb_dispatch_one(struct mnode *mnode_p, uint32_t dispmask)
{
	struct mnode_subscribe *disp_cb;
	
//	log_debug("db_refresh: dtype=[%s]\n",db_name(dtype));
	mlist_for_each_entry(disp_cb, &mnode_p->dispatcher, struct mnode_subscribe, list) {
		// masked, skip
		if ((dispmask & disp_cb->module) == 0) {
			disp_cb->cb(mnode_p->type, disp_cb->userdata);
		}
	}
}


void mdb_dispatch_handle(struct mdb_t *mdb_p)
{
	int i;
	struct mnode *node;

	uint32_t disp = mdb_p->dispatch_need; //when call dipatcher, dispatch_need maybe set again

	mdb_p->dispatch_need = 0;

//	log_info("mdb_dispatch_handle enter\n");

	while(disp) {
		for (i = 0; i < mdb_p->node_num; i++) {
			node = mnode_get(i);
			
			//para = &sys_parameter[i];
			if (node->dirty == 0)
				continue;
			
			mdb_dispatch_one(node, node->dispmask);

			node->dispmask = 0;
			node->dirty = 0;
		}
		disp = mdb_p->dispatch_need;
		mdb_p->dispatch_need = 0;
	}

#if (USE_LVGL_UI == 1)
	lv_timer_pause(mdb_p->dispatch_timer);	
#endif

//	log_info("mdb_dispatch_handle exit\n");

}

void mdb_dispatch_start(struct mnode *node, uint32_t flag)
{
	uint32_t module = GET_MODULE(flag);

	if (flag & FLAG_DISP_MUTE) {
		node->dispmask = module;
	} else {
		node->dispmask = 0;
	}
	node->dirty = 1;
	
#if (USE_LVGL_UI == 1)
	if (mdb_p->dispatch_need == 0) {
		lv_timer_reset(mdb_p->dispatch_timer);
	}
	mdb_p->dispatch_need = 1;
	lv_timer_resume(mdb_p->dispatch_timer);
#endif
//	log_debug("dispatch_start runing\n");
}

/*  do not change data, just call dispatch callback  */
static void mdb_notify_one(struct mnode *mnode_p)
{
	struct mnode_subscribe *listen_cb;
	
//	log_debug("db_refresh: dtype=[%s]\n",db_name(dtype));
	mlist_for_each_entry(listen_cb, &mnode_p->listener, struct mnode_subscribe, list) {
		listen_cb->cb(mnode_p->type, listen_cb->userdata);
	}
}

/*  do not change data, just call listen callback  */
void mdb_refresh(int32_t dtype)
{
	struct mnode *node = mnode_get(dtype);
	
	mdb_notify_one(node);
}


/*  not call dispatcher  */
void __mdb_upt_point(int32_t dtype, int32_t newv)
{
	struct mnode *node = mnode_get(dtype);

//	if (dtype != DATE_CHANGE_FLAG) {
//		log_info("db_upt_point: dtype=[%s] data=[%x]\n",db_name(dtype), newv);
//	}
	
	node->percent = 0;
	node->value = newv;
	
	mdb_notify_one(node);
}

/*  not call dispatcher  */
void mdb_update(int32_t dtype, int32_t newv)
{
	struct mnode *node = mnode_get(dtype);
	
//	if (dtype != DATE_CHANGE_FLAG) {
//		log_info("db_update: dtype=[%s] data=[%d]\n",db_name(dtype), newv);
//	}
	CLIP(newv, node->max, node->min);

	node->percent = DIV((newv - node->min) * 100, (node->max - node->min));
	node->value = newv;

	mdb_notify_one(node);
}

/*  not call dispatcher and listener */
static void mdb_set(int32_t dtype, int32_t newv, uint32_t flag)
{
	struct mnode *node = mnode_get(dtype);

	CLIP(newv, node->max, node->min);

	node->percent = DIV((newv - node->min) * 100, (node->max - node->min));
	node->value = newv;
	
	mdb_notify_one(node);

	mdb_dispatch_start(node, flag);
}


void __mdb_set_external(int32_t dtype, int32_t newv, uint32_t flag)
{
	struct mnode *node = mnode_get(dtype);
	uint32_t module = GET_MODULE(flag);

	if ((node->flag & module) == 0) {
		log_error("__db_set_external error: addr(0x%x) from invalid module(%x)\n", node->addr, module);
		return;
	}
		

//	CLIP(newv, node->max, node->min);
	if (!IN_RANGE(newv, node->max, node->min)) {
		// dispatch to sender
		log_warn("__db_set_external not inrange: dtype=[%s] max=[%d] min=[%d] curv=[%d], newv=[%d], dispmask=[0x%08x]\n",
			mdb_name(dtype), node->max, node->min, node->value, newv, module);
		mdb_dispatch_one(node, ~module);
		return;
	}
	
//	log_debug("__db_set_external: dtype=[%s] curvalue=[%d], newv=[%d]\n",
//		db_name(dtype), node->value, newv);

	node->percent = DIV((newv - node->min) * 100, (node->max - node->min));
	node->value = newv;
	if (node->flag & FLAG_FLOAT_HELP) {
		node->fvalue = (int32_t)DIV(newv * node->fmut, node->fstep) * node->fstep;
	}

	mdb_notify_one(node);

	mdb_dispatch_start(node, flag);
}

void __mdb_set_pctool(int32_t dtype, int32_t newv, uint32_t flag)
{
	struct mnode *node = mnode_get(dtype);
	uint32_t module = GET_MODULE(flag);

//	CLIP(newv, node->max, node->min);
	if (!IN_RANGE(newv, node->max, node->min)) {
		// dispatch to sender
		log_warn("__db_set_external not inrange: dtype=[%s] max=[%d] min=[%d] curv=[%d], newv=[%d], dispmask=[0x%08x]\n",
			mdb_name(dtype), node->max, node->min, node->value, newv, module);
		mdb_dispatch_one(node, ~module);
		return;
	}
	
//	log_debug("__db_set_external: dtype=[%s] curvalue=[%d], newv=[%d]\n",
//		db_name(dtype), node->value, newv);

	node->percent = DIV((newv - node->min) * 100, (node->max - node->min));
	node->value = newv;
	if (node->flag & FLAG_FLOAT_HELP) {
		node->fvalue = (int32_t)DIV(newv * node->fmut, node->fstep) * node->fstep;
	}

	mdb_notify_one(node);

	mdb_dispatch_start(node, flag);
}


void __mdb_set_direct(int32_t dtype, int32_t newv, uint32_t dispmask)
{
//	struct mnode *para = &sys_parameter[dtype];
//	log_debug("db_set_direct: dtype=[%s] curvalue=[%d], newv=[%d]\n",
//		db_name(dtype), node->value, newv);
	
//	if (node->flag & FHELP) {
//		node->fvalue = newv;
//	}
	mdb_set(dtype, newv, dispmask);	
//	log_debug("db_set_direct: clip value=[%d]\n", node->value);
}

void mdb_set_direct(int32_t dtype, int32_t newv)
{
	__mdb_set_direct(dtype, newv, 0);
}


void __mdb_set_step_dec(int32_t dtype, uint16_t nstep, uint32_t dispmask)
{
	int32_t newv; 
	struct mnode *node = mnode_get(dtype);

//	log_debug("db_set_step_dec: dtype=[%s] curvalue=[%d], nstep=[%d]\n",
//		db_name(dtype), node->value, nstep);
	if (node->flag & FLAG_FLOAT_HELP) {
//		log_debug("dec before: fvalue=[%f] fstep=[%f]\n", node->fvalue, node->fstep);
		node->fvalue -= nstep * node->fstep;
//		log_debug("dec after: fvalue=[%f] fstep=[%f]\n", node->fvalue, node->fstep);
		node->fvalue = (int32_t)DIV(node->fvalue, node->fstep) * node->fstep;
		CLIP(node->fvalue, node->fmax, node->fmin);
//		log_debug("dec clip: fvalue=[%f] fstep=[%f]\n", node->fvalue, node->fstep);
		
		newv = DIV(node->fvalue, node->fmut);
	} else {
//		log_debug("dec before: value=[%d] step=[%d]\n", node->value, node->step);
		newv = node->value - nstep * node->step;
//		log_debug("dec after: value=[%d] step=[%d]\n", newv, node->step);
	}

	mdb_set(dtype, newv, dispmask);
}

void mdb_set_step_dec(int32_t dtype, uint16_t nstep)
{
	__mdb_set_step_dec(dtype, nstep, 0);
}

void __mdb_set_step_add(int32_t dtype, uint16_t nstep, uint32_t dispmask)
{
	int32_t newv; 
	struct mnode *node = mnode_get(dtype);
	
//	log_debug("db_set_step_add: dtype=[%s] curvalue=[%d], nstep=[%d]\n",
//		db_name(dtype), node->value, nstep);
	if (node->flag & FLAG_FLOAT_HELP) {
//		log_debug("add before: fvalue=[%f] fstep=[%f]\n", node->fvalue, node->fstep);
		node->fvalue += nstep * node->fstep;
//		log_debug("add after: fvalue=[%f] fstep=[%f]\n", node->fvalue, node->fstep);
		//fnewv = (int)(node->fvalue/node->fstep);
//		fnewv = (int)(227124/37854);
//		log_debug("add after: fnewv=[%d]\n", fnewv);
		node->fvalue = (int32_t)DIV(node->fvalue, node->fstep) * node->fstep;
//		log_debug("add after: fvalue=[%f] fstep=[%f]\n", node->fvalue, node->fstep);
//		log_debug("flow set max=%f, min=%f\n", node->fmax, node->fmin);
		CLIP(node->fvalue, node->fmax, node->fmin);
//		log_debug("add clip: fvalue=[%f] fstep=[%f]\n", node->fvalue, node->fstep);
		
		newv = DIV(node->fvalue, node->fmut);
	} else {
//		log_debug("add before: value=[%d] step=[%d]\n", node->value, node->step);
		newv = node->value + nstep * node->step;
//		log_debug("add after: value=[%d] step=[%d]\n", newv, node->step);
	}
	mdb_set(dtype, newv, dispmask);
}
void mdb_set_step_add(int32_t dtype, uint16_t nstep)
{
	__mdb_set_step_add(dtype, nstep, 0);
}

void __mdb_set_percent(int32_t dtype, uint16_t percent, uint32_t dispmask)
{
	int32_t newv; 
	struct mnode *node = mnode_get(dtype);
	
//	log_debug("db_set_step_add: dtype=[%s] max=[%d], min=[%d], percent=[%3d]\n",
//		db_name(dtype), node->max, node->min, percent);

	if (percent > 100)
		percent = 100;

	if (node->flag & FLAG_FLOAT_HELP) {
		node->fvalue = (node->fmax - node->fmin) * percent / 100 + node->fmin;	
		node->fvalue = (int32_t)DIV(node->fvalue, node->fstep) * node->fstep;
		newv = DIV(node->fvalue, node->fmut);
//		newv = (int)fnewv;
	} else {
		newv = (node->max - node->min) * percent / 100 + node->min;	
	}

	mdb_set(dtype, newv, dispmask);
}

void mdb_set_percent(int32_t dtype, uint16_t percent)
{
	__mdb_set_percent(dtype, percent, 0);
}

void __mdb_set_min_max(int32_t dtype, int32_t max, int32_t min, uint32_t dispmask)
{
	struct mnode *node = mnode_get(dtype);
	
//	log_debug("__db_set_min_max: dtype=[%s] max=[%d], min=[%d], value=[%3d]\n",
//		db_name(dtype), max, min, node->value);

	node->max = max;
	node->min = min;
	
	mdb_set(dtype, node->value, dispmask);
}

void mdb_set_min_max(int32_t dtype, int32_t max, int32_t min)
{
	__mdb_set_min_max(dtype, max, min, 0);
}

void mdb_set_fmin_fmax(int32_t dtype, int32_t max, int32_t min)
{	
	struct mnode *node = mnode_get(dtype);

	node->fmax = max;
	node->fmin = min;
}

void mdb_foreach_call(mdb_callback_t cb, void *userdata)
{
	int i;
	struct mdb_t *mdb_p = mdb_get();

	for (i = 0; i < mdb_p->node_num; i++) {
		if(cb)
			cb(i, userdata);
	}
}

void mdb_dispatch_all(uint32_t dispmask)
{
	int i;
	struct mdb_t *mdb_p = mdb_get();
	struct mnode *node;

	log_debug("dispatch all mask: %x\n", dispmask);
	
	for (i = 0; i < mdb_p->node_num; i++) {
		node = mnode_get(i);
		mdb_dispatch_one(node, dispmask);
	}
}


struct mnode_subscribe *mdb_alloc_subscribe(mdb_callback_t cb, void *userdata)
{
	struct mnode_subscribe *mnode_sub;

	// current not exit, insert it to list
	mnode_sub = mdb_calloc(1, sizeof(struct mnode_subscribe));
	if (!mnode_sub) {
		log_error("mdb_alloc_subscribe failed, mo memory\n");
		return NULL;
	}
	
	mnode_sub->cb = cb;
	mnode_sub->userdata = userdata;

	return mnode_sub;
}

void mdb_register_global_listerner(mdb_callback_t cb, void *userdata)
{
	struct mdb_t *mdb_p = mdb_get();
	struct mnode_subscribe *mnode_sub;

	if ((mnode_sub = mdb_alloc_subscribe(cb, userdata)) == NULL) {
		return;
	}

//	log_debug("db_insert_update: addr=[0x%04x] data=[0x%04x]\n",elem->addr, elem->data);
	mlist_add_tail(&mnode_sub->list, &mdb_p->global_listener);
	
	mdb_foreach_call(cb, userdata);
}

void mdb_register_global_dispatcher(mdb_callback_t cb, void *userdata)
{
	struct mdb_t *mdb_p = mdb_get();
	struct mnode_subscribe *mnode_sub;

	if ((mnode_sub = mdb_alloc_subscribe(cb, userdata)) == NULL) {
		return;
	}
	
	//	log_debug("db_insert_update: addr=[0x%04x] data=[0x%04x]\n",elem->addr, elem->data);
	mlist_add_tail(&mnode_sub->list, &mdb_p->global_dispatcher);
	
	mdb_foreach_call(cb, userdata);
}



void mdb_register_listerner(int32_t dtype, mdb_callback_t cb, void *userdata, uint32_t flag)
{
	struct mnode *node = mnode_get(dtype);
	struct mnode_subscribe *mnode_sub;

	if ((mnode_sub = mdb_alloc_subscribe(cb, userdata)) == NULL) {
		return;
	}
	
	//	log_debug("db_insert_update: addr=[0x%04x] data=[0x%04x]\n",elem->addr, elem->data);
	mlist_add_tail(&mnode_sub->list, &node->dispatcher);

	if ((flag & LISTENER_NOT_CALL) == 0)
		cb(dtype, userdata);
}

void mdb_register_dispatcher(int32_t dtype, mdb_callback_t cb, void *userdata, uint32_t module)
{
	struct mnode *mnode_p= mnode_get(dtype);
	struct mnode_subscribe *mnode_sub;

	if ((mnode_sub = mdb_alloc_subscribe(cb, userdata)) == NULL) {
		return;
	}
	mnode_sub->module = module;
	
	//	log_debug("db_insert_update: addr=[0x%04x] data=[0x%04x]\n",elem->addr, elem->data);
	mlist_add_tail(&mnode_sub->list, &mnode_p->dispatcher);
}

bool mdb_type_valid(int32_t dtype)
{
	struct mdb_t *mdb = mdb_get();

	return dtype >= mdb->node_num;
}

#if (USE_LVGL_UI == 1)

static void mdb_dispatch_timer_cb(struct _lv_timer_t *timer)
{
	struct mdb_t *mdb = timer->user_data;
	
	mdb_dispatch_handle(mdb);	
}

static void mdb_update_timer_cb(struct _lv_timer_t *timer)
{
	struct mdb_t *mdb = timer->user_data;
	
	mdb_update_handle(mdb);	
}

#else

void mdb_handler_loop(void)
{
	struct mdb_t *mdb = mdb_get();

	mdb_dispatch_handle(mdb);	
	mdb_update_handle(mdb);
}
#endif

int mdb_service_init(struct mnode *nodes, uint32_t num)
{
	int i;
	struct mdb_t *mdb = mdb_get();
	OPERATE_RET ret;
		
	ret = mdb_lock_init(&mdb->lock);
	if (ret != OPRT_OK) {
		log_error("mdb init lock error(%d)\n", ret);
		return -1;
	}	
	mdb->dispatch_need = 0;
	MINIT_LIST_HEAD(&mdb->update_list);
	MINIT_LIST_HEAD(&mdb->global_listener);
	MINIT_LIST_HEAD(&mdb->global_dispatcher);
	mdb->node_num = num;
	mdb->nodes = nodes;
	for (i = 0; i < num; i++) {
		MINIT_LIST_HEAD(&nodes->listener);
		MINIT_LIST_HEAD(&nodes->dispatcher);
		nodes->value = nodes->defv;
		nodes->percent = DIV((nodes->value - nodes->min) * 100, (nodes->max - nodes->min));
		if (nodes->flag & FLAG_FLOAT_HELP) {
			nodes->fvalue = nodes->value;
		}
		nodes++;
	}

#if (USE_LVGL_UI == 1)
	mdb->dispatch_timer = lv_timer_create(mdb_dispatch_timer_cb, 500, mdb);
	mdb->update_timer = lv_timer_create(mdb_update_timer_cb, 10, mdb);
#endif

}
